package com.summer;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author chanchaw
 * @create 2022-10-10 10:21
 */
public class SummerApplicationContext {
    private Class configClass = null;

    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String,Object> singletonObjects = new HashMap<>();// 单例池

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public SummerApplicationContext(Class configClass) {
        this.configClass = configClass;
        scanAndBuildBeanDefinitionMap();// 扫描配置类头上注解@ComponentScan指定的包路径
        createSingletonObjects();// 根据 BeanDefiniction 创建单例对象并纳入单例池
    }

    private void createSingletonObjects(){// 创建单例对象
        if(beanDefinitionMap == null || beanDefinitionMap.size()<=0) return;
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            // 是单例的实体类才需要纳入单例池
            if(beanDefinition.getScope().equalsIgnoreCase("singleton")){
                Object bean = createBean(beanDefinition.getDefaultBeanName(),beanDefinition);
                singletonObjects.put(beanDefinition.getDefaultBeanName(),bean);
            }
        }
    }

    public Object getBean(String beanName){
        if(!beanDefinitionMap.containsKey(beanName))
            throw new RuntimeException("名称为[" + beanName + "]的 bean 在容器内不存在！");
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(!beanDefinition.getScope().equalsIgnoreCase("singleton")) return createBean(beanName,beanDefinition);
        // 所有单例对象都应该在框架初始化时创建，所以这里默认已经存在该名称的 bean
        Object o = singletonObjects.get(beanName);
        if(o == null){
            o = createBean(beanName,beanDefinition);
        }
        return o;
    }

    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object o = null;
        try {
            o = clazz.getConstructor().newInstance();

            // 解决依赖注入
            for (Field field : clazz.getDeclaredFields()) {
                if(!field.isAnnotationPresent(Autowired.class))
                    continue;
                field.setAccessible(true);
                String fieldName = field.getName();
                field.set(o,getBean(fieldName));
            }

            // 执行 BeanPostProcessor # postProcessBeforeInitialization
            for (BeanPostProcessor processor : beanPostProcessorList) {
                o = processor.postProcessBeforeInitialization(o,beanName);
            }

            // 依赖注入后调用执行 InitializingBean # afterPropertiesSet
            if(o instanceof InitializingBean){
                ((InitializingBean)o).afterPropertiesSet();
            }

            // 执行 BeanPostProcessor # postProcessAfterInitialization
            for (BeanPostProcessor processor : beanPostProcessorList) {
                o = processor.postProcessAfterInitialization(o,beanName);
            }

        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return o;
    }

    // 扫描配置类头上的注解 @ComponentScan 配置的包路径，创建 beans
    private void scanAndBuildBeanDefinitionMap(){
        if(configClass == null) return;
        if(configClass.isAnnotationPresent(ComponentScan.class)){
            ComponentScan annotation = (ComponentScan)configClass.getAnnotation(ComponentScan.class);
            String beanPackage = annotation.value();// 拿到包路径
            System.out.println("配置类设置了包路径：" + beanPackage);

            // 包路径转换为文件夹路径
            // com.chanchaw.service = com/chanchaw/service
            String path = beanPackage.replace(".","/");

            // 通过 ClassLoader 获取文件 - 编译后的
            ClassLoader classLoader = SummerApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);

            // 遍历指定目录下的所有文件
            File file = new File(resource.getFile());
            if(file.isDirectory())
                for (File f : file.listFiles()) {
                    // 编译后 class 文件的完整路径：D:\software\summer\target\classes\com\chanchaw\service\OrderService.class
                    String absolutePath = f.getAbsolutePath();
                    // 将上面的文件完整路径转换为包路径+类名称：com.chanchaw.service.OrderService
                    String packageAndClass = getPackagePath(absolutePath);
                    try {
                        Class<?> clazz = classLoader.loadClass(packageAndClass);
                        // 使用了注解 @Component，并且没有设置 @Scope("prototype")
                        // 则在容器中创建实例对象
                        if(clazz.isAnnotationPresent(Component.class)){
                            // 缓存 BeanPostProcessor
                            if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                                BeanPostProcessor beanPostProcessor = (BeanPostProcessor)clazz.getConstructor().newInstance();
                                beanPostProcessorList.add(beanPostProcessor);
                            }

                            // 构建 BeanDefinition 对象并缓存起来
                            BeanDefinition beanDefinition = getBeanDefinition(clazz);
                            beanDefinitionMap.put(beanDefinition.getDefaultBeanName(),beanDefinition);
                        }
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }

                }
        }
    }

    // 传入文件完整路径，转换为类的包路径
    // 传入：D:\software\summer\target\classes\com\chanchaw\service\OrderService.class
    // 返回：com.chanchaw.service.OrderService
    private String getPackagePath(String absolutePath){
        int classesIndex = absolutePath.indexOf("classes\\");
        int classIndex = absolutePath.indexOf(".class");
        String subString = absolutePath.substring(classesIndex + 8,classIndex);
        return subString.replace("\\",".");
    }

    // 返回 true 表示实体类被标注了Scope=prototype
    // 不需要在容器中创建实例对象
    private Boolean isProtoType(Class clazz){
        if(!clazz.isAnnotationPresent(Scope.class)) return false;
        Scope annotation = (Scope)clazz.getAnnotation(Scope.class);
        String value = annotation.value();
        return value.equalsIgnoreCase("prototype") ? true : false;
    }

    // 默认singleton
    private String getScopeString(Class clazz){
        if(!clazz.isAnnotationPresent(Scope.class)) return "singleton";
        Scope annotation = (Scope)clazz.getAnnotation(Scope.class);
        String scope = annotation.value();
        if(scope == null) return "singleton";
        if(scope.length()<=0) return "singleton";
        return scope;
    }

    private boolean isLazy(Class clazz){
        if(!clazz.isAnnotationPresent(Lazy.class)) return false;
        Lazy annotation = (Lazy)clazz.getAnnotation(Lazy.class);
        return annotation.value();
    }

    private BeanDefinition getBeanDefinition(Class clazz){
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setType(clazz);
        beanDefinition.setScope(getScopeString(clazz));
        beanDefinition.setLazy(isLazy(clazz));

        String[] strArray = clazz.getName().split("\\.");
        String className = strArray[strArray.length-1];
        String defaultBeanName = className.substring(0,1).toLowerCase() + className.substring(1);
        beanDefinition.setDefaultBeanName(defaultBeanName);
        System.out.println("defaultBeanName：" + defaultBeanName);
        return beanDefinition;
    }
}
